/*
 * Copyright 2008 Zoundry LLC, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.zoundry.gaedt.userinput;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import org.zoundry.gaedt.core.util.ZGaeUtil;

/**
 * The user input service is a simple service that sets up a listening
 * socket, listens for requests, prompts the user for some input, then
 * returns the result back to the calling application.
 *
 * FIXME optionally allow a value to be cached (keyed off message text?)
 */
public class ZGaeUserInputService implements Runnable
{
    /** Singleton instance. */
    private static final ZGaeUserInputService sInstance = new ZGaeUserInputService();

    /**
     * Singleton getter.
     */
    public static final ZGaeUserInputService getInstance()
    {
        return sInstance;
    }

    private Thread mThread;
    private ServerSocket mServerSocket;
    private int mServerPort;
    private boolean mStopped;

    /**
     * C'tor.
     */
    public ZGaeUserInputService()
    {
    }

    /**
     * Starts the user input service.
     */
    public void start()
    {
        mThread = new Thread(this);
        mThread.setDaemon(true);
        mThread.setName(this.getClass().getSimpleName() + "Thread");
        mStopped = false;
        mThread.start();
    }

    /**
     * Stops the user input service.
     */
    public void stop()
    {
        mStopped = true;
        try
        {
            if (mServerSocket != null)
                mServerSocket.close();
        }
        catch (IOException e)
        {
            ZGaeUtil.logError(e);
        }
    }

    /**
     * @see java.lang.Runnable#run()
     */
    public void run()
    {
        createServerSocket();
        while (!mStopped)
        {
            try
            {
                Socket clientSocket = mServerSocket.accept();
                handleClientConnect(clientSocket);
            }
            catch (SocketException se)
            {
                // Server socket was closed.
            }
            catch (Exception e)
            {
                ZGaeUtil.logError(e);
            }
        }
    }

    /**
     * Creates the server socket on any free port.
     */
    private void createServerSocket()
    {
        try
        {
            mServerSocket = new ServerSocket(0);
            mServerPort = mServerSocket.getLocalPort();
        }
        catch (IOException e)
        {
            ZGaeUtil.logError(e);
            mStopped = true;
        }
    }

    /**
     * Handles a single client connection to the service.
     *
     * @param aClientSocket
     * @throws IOException
     */
    private void handleClientConnect(Socket aClientSocket) throws IOException
    {
        try
        {
            aClientSocket.setSoTimeout(3000);
            Map<String, String> requestArgs = readRequestArgs(aClientSocket);
            String response = doRequest(requestArgs);
            writeResponse(aClientSocket, response);
        }
        finally
        {
            aClientSocket.close();
        }
    }

    /**
     * Reads the arguments from the request.
     *
     * @param aClientSocket
     * @throws IOException
     */
    private Map<String, String> readRequestArgs(Socket aClientSocket) throws IOException
    {
        Map<String, String> args = new HashMap<String, String>();
        InputStream inputStream = aClientSocket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        boolean done = false;
        while (!done)
        {
            String line = reader.readLine().trim();
            if (".".equals(line))
                done = true;
            else
            {
                int idx = line.indexOf('=');
                if (idx > 0)
                {
                    String key = line.substring(0, idx).trim();
                    String value = line.substring(idx + 1).trim();
                    args.put(key, value);
                }
            }
        }
        return args;
    }

    /**
     * Do the actual request.  This will prompt the user for input.
     *
     * @param aRequestArgs
     */
    private String doRequest(Map<String, String> aRequestArgs)
    {
        String title = aRequestArgs.get("title");
        String message = aRequestArgs.get("message");
        boolean isPassword = Boolean.valueOf(aRequestArgs.get("password"));
        ZGaeUserInputPrompter prompter = new ZGaeUserInputPrompter(title, message, isPassword);
        return prompter.prompt();
    }

    /**
     * Write the response back to the client socket.
     *
     * @param aClientSocket
     * @param aResponse
     * @throws IOException
     */
    private void writeResponse(Socket aClientSocket, String aResponse) throws IOException
    {
        OutputStream outputStream = aClientSocket.getOutputStream();
        PrintWriter writer = new PrintWriter(outputStream);
        if (aResponse == null)
            writer.write("___noinput___");
        else
            writer.write(aResponse);
        writer.flush();
    }

    /**
     * Returns the port on which this service is listening.
     */
    public int getServicePort()
    {
        return mServerPort;
    }
}
